﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using NativeWiFiLibrary;
using System.Windows.Threading;
using WiFiChannelSpread.GoogleApi;
using System.Threading;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Globalization;

namespace WiFiChannelSpread
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private bool _pauseUpdates;
        private bool _forceUpdate;
        private DateTime _previousUpdate;
        private DispatcherTimer _updateTimer;
        private Dictionary<int, ChannelBar> ChannelBars;

        private int NumberOfChannels
        {
            get { return ChannelGrid.ColumnDefinitions.Count; }
        }

        private double ChannelRenderWidth
        {
            get { return MainGrid.ActualWidth / NumberOfChannels; }
        }

        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                CreateChannelBars();

                _pauseUpdates = false;
                _forceUpdate = true;
                this._updateTimer = new DispatcherTimer();
                this._updateTimer.Tick += new EventHandler(UpdateTimer_Tick);
                this._updateTimer.Interval = TimeSpan.FromMilliseconds(500);
                this._updateTimer.Start();
            }
            catch (Exception ex)
            {
                ReportError("Failed to initialise application.", ex);
            }
        }

        void UpdateTimer_Tick(object sender, EventArgs e)
        {
            if (!_pauseUpdates &&
                (DateTime.Now.Subtract(_previousUpdate).TotalSeconds >= 30 || _forceUpdate))
            {
                _forceUpdate = false;
                UpdateAccessPoints();
                _previousUpdate = DateTime.Now;
            }
        }

        private void UpdateAccessPoints()
        {
            try
            {
                if (WlanHelper.SelectedNetworkDevice == null)
                {
                    ReportError("No wireless network devices were found.");
                }
                else
                {
                    //WlanHelper.SelectedNetworkDevice.Scan();

                    Dictionary<int, double> ChannelLevel = new Dictionary<int, double>(NumberOfChannels);
                    Dictionary<int, double> CumulativeChannelStrength = new Dictionary<int, double>();
                    Dictionary<int, int> AirportCount = new Dictionary<int, int>();

                    for (int i = 0; i < NumberOfChannels; i++)
                    {
                        ChannelLevel.Add(i, 0);
                        AirportCount.Add(i, 0);
                        CumulativeChannelStrength.Add(i, 0);
                    }

                    List<AccessPoint> accessPoints = new List<AccessPoint>();
                    try
                    {
                        foreach (Network network in WlanHelper.SelectedNetworkDevice.GetAvailableNetworks())
                        {
                            foreach (AccessPoint ap in network.GetAccessPoints())
                            {
                                accessPoints.Add(ap);
                            }
                        }
                    }
                    catch (WlanException ex)
                    {
                        ReportError("Failed to load available networks.", ex);
                    }

                    // Filter out duplicate access points
                    int indexToRemove;
                    for (int i = 0; i < accessPoints.Count - 1; i++)
                    {
                        for (int j = i + 1; j < accessPoints.Count; j++)
                        {
                            if (accessPoints[i].MacAddress.Equals(accessPoints[j].MacAddress, StringComparison.OrdinalIgnoreCase))
                            {
                                if (accessPoints[i].Network.IsConnected) indexToRemove = j;
                                else if (accessPoints[j].Network.IsConnected) indexToRemove = i;
                                else if (accessPoints[i].Network.HasProfile) indexToRemove = j;
                                else if (accessPoints[j].Network.HasProfile) indexToRemove = i;
                                else if (!string.IsNullOrEmpty(accessPoints[i].Network.Ssid)) indexToRemove = j;
                                else if (!string.IsNullOrEmpty(accessPoints[j].Network.Ssid)) indexToRemove = i;
                                else indexToRemove = j;

                                accessPoints.RemoveAt(indexToRemove);
                                if (indexToRemove <= i) i--;
                                if (indexToRemove <= j) j--;
                            }
                        }
                    }

                    // Sort by link quality, strongest first
                    accessPoints.Sort((a, b) => -a.LinkQuality.CompareTo(b.LinkQuality));

                    // Focus map
                    if (accessPoints.Count > 0)
                    {
                        ThreadPool.QueueUserWorkItem((s) => { TryShowBackgroundMap(accessPoints); });
                    }

                    AccessPointControl apc;

                    // Remove old access point controls
                    for (int i = 0; i < AccessPointGrid.Children.Count; i++)
                    {
                        apc = AccessPointGrid.Children[i] as AccessPointControl;
                        if (apc != null)
                        {
                            AccessPointGrid.Children.RemoveAt(i--);
                        }
                    }

                    double airportChannelLevel = 0;
                    int channelIndex;
                    foreach (AccessPoint ap in accessPoints)
                    {
                        channelIndex = ap.Channel - 1;

                        airportChannelLevel = 0;
                        for (int i = channelIndex; i < channelIndex + 5; i++)
                        {
                            AirportCount[i]++;
                            CumulativeChannelStrength[i] += ap.LinkQuality * (1.0 / (1.0 + Math.Abs(i - channelIndex - 2)));
                            if (ChannelLevel[i] > airportChannelLevel)
                            {
                                airportChannelLevel = ChannelLevel[i];
                            }
                        }

                        for (int i = channelIndex; i < channelIndex + 5; i++)
                        {
                            ChannelLevel[i] = airportChannelLevel + 35 + ap.LinkQuality;
                        }

                        apc = new AccessPointControl(ap);
                        apc.StatusChanged += new StatusChangeEventHandler(apc_StatusChanged);
                        apc.ExceptionHandler += new ExceptionEventHandler(apc_ExceptionHandler);
                        apc.Margin = new Thickness(0, -airportChannelLevel, 0, airportChannelLevel);

                        AccessPointGrid.Children.Insert(0, apc);
                        Grid.SetColumn(apc, channelIndex);
                        Grid.SetColumnSpan(apc, 5);
                    }

                    // Display channel interference
                    double newOpacity;
                    for (int i = 0; i < NumberOfChannels; i++)
                    {
                        if (AirportCount[i] > 1)
                        {
                            newOpacity = ((AirportCount[i] - 1) / 10.0) + ((CumulativeChannelStrength[i] / AirportCount[i]) / 150.0);
                        }
                        else
                        {
                            newOpacity = 0;
                        }

                        AnimationHelper.FadeIn(ChannelBars[i].StaticBar, newOpacity);
                    }

                    // Adjust window height if necessary
                    double maxChannelLevel = ChannelLevel.Values.Max();
                    if (80 + maxChannelLevel > this.ActualHeight)
                    {
                        AnimationHelper.StartDoubleAnimation(
                            this,
                            Window.HeightProperty,
                            60 + maxChannelLevel,
                            1000);
                    }
                }
            }
            catch (Exception ex)
            {
                ReportError("Failed to update list of access points.", ex);
            }
        }

        private void TryShowBackgroundMap(IEnumerable<AccessPoint> accessPoints)
        {
            Collection<WiFiTower> wiFiTowers = new Collection<WiFiTower>();
            foreach (AccessPoint ap in accessPoints)
            {
                wiFiTowers.Add(new WiFiTower { mac_address = ap.MacAddress, ssid = ap.Ssid, signal_strength = ap.Rssi.ToString() });
            }

            Location location = null;
            try
            {
                location = GeolocationApi.GetGeolocation(wiFiTowers, false);
            }
            catch (Exception ex)
            {
                ReportError("Failed to load geolocation.", ex);
            }

            if (location != null && !string.IsNullOrEmpty(location.longitude) && !string.IsNullOrEmpty(location.latitude))
            {
                // Only show map when ready loading tiles
                BackgroundMap.OnTileLoadComplete += delegate { Dispatcher.BeginInvoke(new Action(() => { AnimationHelper.Fade(BackgroundImage, 0.8); })); };

                // Determine zoom level, ideally based on geolocation accuracy
                double newZoomLevel = 15;
                if (!string.IsNullOrEmpty(location.accuracy))
                {
                    int accuracy = int.Parse(location.accuracy);
                    newZoomLevel = 15 - (accuracy / 250.0);
                    if (newZoomLevel < 8)
                    {
                        newZoomLevel = 8;
                    }
                }

                Dispatcher.BeginInvoke(new Action(() =>
                    {
                        BackgroundMap.Manager.Mode = GMap.NET.AccessMode.ServerOnly;
                        BackgroundMap.MapType = GMap.NET.MapType.GoogleHybrid;
                        BackgroundMap.CurrentPosition = new GMap.NET.PointLatLng(double.Parse(location.latitude, CultureInfo.InvariantCulture), double.Parse(location.longitude, CultureInfo.InvariantCulture));
                        BackgroundMap.Zoom = newZoomLevel;
                    }));
            }
        }

        void apc_StatusChanged(object sender, StatusChangeAction requiredAction)
        {
            switch (requiredAction)
            {
                case StatusChangeAction.UpdateAsap:
                    _forceUpdate = true;
                    _pauseUpdates = false;
                    break;
                case StatusChangeAction.PauseUpdates:
                    _pauseUpdates = true;
                    _forceUpdate = false;
                    break;
            }
        }

        void apc_ExceptionHandler(object sender, string message, Exception exception)
        {
            ReportError(message, exception);
        }

        private void CreateChannelBars()
        {
            ChannelBars = new Dictionary<int, ChannelBar>(NumberOfChannels);
            for (int i = 0; i < NumberOfChannels; i++)
            {
                ChannelBar cb = new ChannelBar();
                ChannelGrid.Children.Add(cb);
                Grid.SetColumn(cb, i);
                ChannelBars.Add(i, cb);

                ChannelHeader ch = new ChannelHeader(i - 1);
                AccessPointGrid.Children.Add(ch);
                Grid.SetColumn(ch, i);
            }
        }

        private void ReportError(string message)
        {
            ReportError(message, null);
        }

        private void ReportError(string message, Exception ex)
        {
            Dispatcher.BeginInvoke(new Action(() => 
                {
                    ErrorLabel.Tag = ex;
                    ErrorLabel.Content = message;
                    AnimationHelper.StartDoubleAnimation(ErrorSection, Border.HeightProperty, ErrorSection.MaxHeight);
                }));
        }

        private void ErrorLabel_MouseDown(object sender, MouseButtonEventArgs e)
        {
            string message = ErrorLabel.Content.ToString();
            if (ErrorLabel.Tag != null)
            {
                message += Environment.NewLine + Environment.NewLine + (ErrorLabel.Tag as Exception).ToString();
            }

            MessageBox.Show(this, message, this.Title, MessageBoxButton.OK, MessageBoxImage.Error);
        }

        private void ErrorClose_MouseDown(object sender, MouseButtonEventArgs e)
        {
            AnimationHelper.StartDoubleAnimation(ErrorSection, Border.HeightProperty, 0);
        }
    }
}
